package server.conn.user;

import java.io.IOException;
import java.net.Socket;
import java.sql.SQLException;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;

import server.conn.ConnectionThread;
import server.order.LimitOrder;
import server.order.Order;
import server.order.TrailingStopOrder;
import server.price.Price;
import server.price.PriceController;
import server.user.BadUserException;
import server.user.InvalidOrderException;
import server.user.User;

public class UserConnectionThread extends ConnectionThread {
	public static final int ACCOUNT_GET_ALL = 101;
	public static final int ACCOUNT_GET_ONE = 102;
	public static final int ACCOUNT_GET_HISTORY = 103;
	
	public static final int USER_GET_INFO = 201;
	public static final int USER_SET_INFO = 202;
	public static final int USER_CHANGE_PASS = 203;
	
	public static final int ORDER_GET_OPEN = 301;
	public static final int ORDER_GET_ONE = 302;
	public static final int ORDER_GET_CLOSED = 303;
	public static final int ORDER_OPEN = 304;
	public static final int ORDER_CANCEL = 305;
	
	public static final int PRICE_GET_INFO = 401;
	public static final int PRICE_GET_HISTORY = 402;
	public static final int PRICE_GET_CURRENCIES = 403;
	
	public UserConnectionThread(Socket socket, UserConnectionController parent, ThreadGroup group) {
		super(socket, parent, group, "UserConnectionThread");
	}
	
	/**
	 * Check user login
	 * @see server.conn.ConnectionThread#checkLogin()
	 */
	protected void checkLogin() {
		printLine(OK);
		
		try {
			String in = readLine();
			StringTokenizer strTok = new StringTokenizer(in, SPLIT);
			if (strTok.nextToken().equals(LOGIN)) {
				String username, password;
				username = strTok.nextToken();
				password = strTok.nextToken();
				
				user = new User(username, password);

				if (parent.checkForUserName(username)) {
					printError("You are already logged in somewhere else!", 3);
				} else if (user.isAdmin()) {
					printError("Admins are not allowed to log in as users!", 3);
				} else {
					checkable = true;
					doCommands();
				}
			} else {
				printError("You have to login before doing anything", 3);
			}
		} catch (IOException e) {
			printError("IOException when reading input", 2);
		} catch (NoSuchElementException e) {
			printError("Not enough arguements for login", 3);
		} catch (SQLException e) {
			printError("SQL exception in UserConnectionThread!", 2);
		} catch (BadUserException e) {
			printError(e.getMessage(), 3);
		}
	}
	
	/**
	 * Listen for commands and execute
	 * @see server.conn.ConnectionThread#checkLogin()
	 */
	protected void doCommands() {
		printLine(OK);
		working = true;
		while (working) {
			try {
				int command;

				StringTokenizer strTok = new StringTokenizer(readLine(), SPLIT);
				command = new Integer(strTok.nextToken());
				
				switch (command) {
				case EXIT:
					printLine(BYE);
					working = false;
					break;
					
				case ACCOUNT_GET_ALL:
					accountGetAll();
					break;
					
				case ACCOUNT_GET_ONE:
					accountGetOne(strTok.nextToken());
					break;
					
				case ACCOUNT_GET_HISTORY:
					accountGetHistory(strTok.nextToken(), new Integer(strTok.nextToken()));
					break;
				
				case USER_GET_INFO:
					printLine(user.toString());
					break;
					
				case USER_SET_INFO:
					break;
				
				case USER_CHANGE_PASS:
					break;
					
				case ORDER_GET_OPEN:
					orderGetOpen();
					break;
					
				case ORDER_GET_ONE:
					orderGetOne(new Integer(strTok.nextToken()));
					break;
					
				case ORDER_GET_CLOSED:
					orderGetClosed(new Integer(strTok.nextToken()));
					break;
					
				case ORDER_OPEN:
					orderOpen(strTok);
					break;
					
				case ORDER_CANCEL:
					orderCancel(new Integer(strTok.nextToken()));
					break;
					
				case PRICE_GET_INFO:
					priceGetInfo(strTok.nextToken(), strTok.nextToken());
					break;
					
				case PRICE_GET_HISTORY:
					priceGetHistory(strTok.nextToken(), strTok.nextToken(), new Integer(strTok.nextToken()), new Integer(strTok.nextToken()));
					break;
					
				case PRICE_GET_CURRENCIES:
					priceGetCurrencies();
					break;
					
				default:
					printLine(ERROR+SPLIT+"Unknown command.");
					break;
				}
			} catch (IOException e) {
				printError("IO exception in UserConnectionThread", 2);
				working = false;
			} catch (NoSuchElementException e) {
				printError("Not enough arguements for requested command!", 4);
			} catch (NumberFormatException e) {
				printError("Bad arguments given!", 4);
			}
			printLine(OK);
		}
	}
	
	// -- ACCOUNT SECTION -------
	
	/**
	 * Print all of the users account over the connection
	 */
	private void accountGetAll() {
		printArray(user.accountsToString());
	}
	
	/**
	 * Print the information of one account over the connection
	 * @param input		Symbol of account to get
	 */
	private void accountGetOne(String sym) {
		printLine(user.accountToString(sym));
	}
	
	/**
	 * Print the history of one account over the connection
	 * @param sym		Symbol of account
	 * @param limit		Max amount of records to return
	 */
	private void accountGetHistory(String sym, int limit) {
		try {
			printArray(user.accountHistoryString(sym, limit));
		} catch (SQLException e) {
			printError("Could not get account history", 3);
		}
	}
	
	// -- USER INFO SECTION -----
	
	
	// -- ORDER SECTION ---------
	
	/**
	 * Print all open orders for the user over the connection
	 */
	private void orderGetOpen() {
		try {
			printArray(user.getOpenOrders());
		} catch (SQLException e) {
			printError("Could not get open orders", 3);
			//e.printStackTrace();
		}
	}
	
	/**
	 * Print the information for one order
	 * @param orderId	Order ID of the requested order
	 */
	private void orderGetOne(int orderId) {
		try {
			Order order = Order.getOrder(orderId);
			
			if (order.getUserId() != user.getID())
				printError("This is not your order", 3);
			else
				printLine(order.toString());
		} catch (SQLException e) {
			printError("Could not get order info", 3);
			e.printStackTrace();
		}
	}
	
	/**
	 * Get latest closed orders
	 * @param limit		Max orders to return
	 */
	private void orderGetClosed(int limit) {
		try {
			printArray(user.getClosedOrders(limit));
		} catch (SQLException e) {
			printError("Could not get closed orders", 3);
		}
	}
	
	/**
	 * Open a new order for user
	 * @param strTok	StringTokenizer that holds the new order information
	 * @see StringTokenizer
	 */
	private void orderOpen(StringTokenizer strTok) {
		try {
			String cur1 = strTok.nextToken();
			String cur2 = strTok.nextToken();
			float rate = new Float(strTok.nextToken());
			float amount = new Float(strTok.nextToken());
			int bType = new Integer(strTok.nextToken());
			int oType = new Integer(strTok.nextToken());
			
			switch (oType) {
			case Order.ORD_MARKET:
			case Order.ORD_LIMIT:
				user.openOrder(new LimitOrder(cur1, cur2, rate, amount, bType, user.getID()));
				printLine("Order created");
				break;
			case Order.ORD_TRAILING_STOP:
				float trailPercent = new Float(strTok.nextToken());
				user.openOrder(new TrailingStopOrder(cur1, cur2, amount, bType, user.getID(), trailPercent));
				printLine("Order created");
				break;
			default:
				printError("Bad order type", 4);
				break;
			}
		} catch (NoSuchElementException e) {
			printError("Mal-formed order statement", 4);
		} catch (InvalidOrderException e) {
			printError(e.getMessage(), 4);
		} catch (SQLException e) {
			printError("Could not add order", 3);
		}
	}
	
	/**
	 * Cancel a users order
	 * @param orderId	The ID of the order to be cancelled
	 */
	private void orderCancel(int orderId) {
		Order order;
		try {
			order = Order.getOrder(orderId);
			
			if (order.getUserId() == user.getID()) {
				if (order.cancelOrder())
					printLine("Order canceled");
				else
					printError("Could not cancel order (OrderController)", 3);
			} else {
				printError("This is not your order", 4);
			}
		} catch (SQLException e) {
			printError("Could not cancel order (SQL)", 3);
		}
	}
	
	// -- PRICE SECTION ---------
	
	/**
	 * Print information for a given price
	 */
	private void priceGetInfo(String cur1, String cur2) {
		Price price = PriceController.getInstance().getPrice(cur1, cur2);

		if (price == null)
			printError(cur1+"/"+cur2+" does not exist", 4);
		else
			printLine(price.toString());	
	}
	
	/**
	 * Get the history of a given price
	 * @param cur1		Base currency
	 * @param cur2		Quote currency
	 * @param limit		Max records to return
	 * @param bType		Which buy/sell type to return
	 */
	private void priceGetHistory(String cur1, String cur2, int limit, int bType) {
		Price price = PriceController.getInstance().getPrice(cur1, cur2);
		
		if (price == null)
			printError(cur1+"/"+cur2+" does not exist", 4);
		else {
			try {
				printArray(price.historyToString(limit, bType));
			} catch (SQLException e) {
				printError("Could not get price history", 3);
			}
		}	
	}
	
	/**
	 * Return line by line each of ACE's currencies
	 */
	private void priceGetCurrencies() {	
		printArray(PriceController.getInstance().getCurrencies());
	}
}
